Skip to content

Conversation

@iovoid
Copy link
Contributor

@iovoid iovoid commented Nov 4, 2025

Motivation

Currently, allocation of a new stack object is taking ~8% of execution.

Description

We make a single allocation of every layer.

@github-actions
Copy link

github-actions bot commented Nov 4, 2025

Lines of code report

Total lines added: 24
Total lines removed: 0
Total lines changed: 24

Detailed view
+---------------------------------------+-------+------+
| File                                  | Lines | Diff |
+---------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/mod.rs | 650   | +24  |
+---------------------------------------+-------+------+

@github-actions
Copy link

github-actions bot commented Nov 4, 2025

Benchmark Results Comparison

No significant difference was registered for any benchmark run.

Detailed Results

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
main_revm_BubbleSort 4.740 ± 0.014 4.725 4.763 1.00 ± 0.00
main_levm_BubbleSort 6.287 ± 0.025 6.261 6.333 1.33 ± 0.01
pr_revm_BubbleSort 4.735 ± 0.016 4.719 4.762 1.00
pr_levm_BubbleSort 6.198 ± 0.021 6.178 6.236 1.31 ± 0.01

Benchmark Results: ERC20Approval

Command Mean [s] Min [s] Max [s] Relative
main_revm_ERC20Approval 1.556 ± 0.007 1.547 1.570 1.01 ± 0.01
main_levm_ERC20Approval 3.170 ± 0.012 3.155 3.193 2.06 ± 0.02
pr_revm_ERC20Approval 1.542 ± 0.015 1.535 1.584 1.00
pr_levm_ERC20Approval 3.175 ± 0.012 3.155 3.198 2.06 ± 0.02

Benchmark Results: ERC20Mint

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Mint 188.1 ± 1.4 186.8 191.0 1.02 ± 0.01
main_levm_ERC20Mint 1650.7 ± 8.4 1640.2 1666.1 8.97 ± 0.05
pr_revm_ERC20Mint 184.0 ± 0.3 183.5 184.7 1.00
pr_levm_ERC20Mint 1670.1 ± 30.5 1643.4 1737.7 9.08 ± 0.17

Benchmark Results: ERC20Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Transfer 352.0 ± 1.4 350.3 354.8 1.00
main_levm_ERC20Transfer 1867.7 ± 16.3 1852.5 1903.7 5.31 ± 0.05
pr_revm_ERC20Transfer 354.2 ± 5.8 349.3 363.5 1.01 ± 0.02
pr_levm_ERC20Transfer 1843.9 ± 11.8 1831.1 1869.6 5.24 ± 0.04

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Factorial 237.8 ± 9.5 234.0 264.7 1.02 ± 0.04
main_levm_Factorial 74421.2 ± 951.7 72679.8 75764.2 318.14 ± 4.19
pr_revm_Factorial 233.9 ± 0.7 232.6 234.9 1.00
pr_levm_Factorial 74630.1 ± 890.8 72792.0 76017.1 319.04 ± 3.94

Benchmark Results: FactorialRecursive

Command Mean [s] Min [s] Max [s] Relative
main_revm_FactorialRecursive 1.669 ± 0.031 1.633 1.733 1.02 ± 0.03
main_levm_FactorialRecursive 73.977 ± 0.219 73.734 74.294 45.06 ± 0.94
pr_revm_FactorialRecursive 1.642 ± 0.034 1.562 1.681 1.00
pr_levm_FactorialRecursive 74.185 ± 0.385 73.736 74.791 45.18 ± 0.97

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Fibonacci 213.2 ± 2.3 211.3 219.0 1.00 ± 0.01
main_levm_Fibonacci 70183.1 ± 305.4 69870.6 70752.3 329.78 ± 2.95
pr_revm_Fibonacci 212.8 ± 1.7 211.7 217.2 1.00
pr_levm_Fibonacci 72206.5 ± 1523.9 70082.7 75232.1 339.29 ± 7.64

Benchmark Results: FibonacciRecursive

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_FibonacciRecursive 861.3 ± 7.6 849.9 873.7 1.00 ± 0.01
main_levm_FibonacciRecursive 2294.4 ± 19.8 2270.4 2335.4 2.67 ± 0.03
pr_revm_FibonacciRecursive 860.3 ± 7.4 849.0 874.6 1.00
pr_levm_FibonacciRecursive 2291.8 ± 29.9 2244.3 2331.0 2.66 ± 0.04

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ManyHashes 12.5 ± 0.2 12.4 13.0 1.00 ± 0.02
main_levm_ManyHashes 1442.4 ± 10.6 1431.6 1465.5 115.64 ± 1.07
pr_revm_ManyHashes 12.5 ± 0.1 12.4 12.6 1.00
pr_levm_ManyHashes 1443.1 ± 6.3 1435.9 1456.4 115.70 ± 0.83

Benchmark Results: MstoreBench

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_MstoreBench 264.9 ± 0.9 263.6 266.7 1.00
main_levm_MstoreBench 72617.5 ± 273.5 72179.4 73144.0 274.16 ± 1.41
pr_revm_MstoreBench 266.1 ± 3.8 263.0 273.2 1.00 ± 0.01
pr_levm_MstoreBench 72196.1 ± 524.0 71461.4 72898.9 272.57 ± 2.20

Benchmark Results: Push

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Push 309.5 ± 4.5 306.5 321.7 1.02 ± 0.02
main_levm_Push 72892.2 ± 502.5 71886.5 73558.4 239.22 ± 4.61
pr_revm_Push 304.7 ± 5.5 300.2 318.8 1.00
pr_levm_Push 72298.0 ± 360.7 71817.6 73087.6 237.27 ± 4.44

Benchmark Results: SstoreBench_no_opt

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_SstoreBench_no_opt 219.6 ± 0.7 218.5 221.1 1.00
main_levm_SstoreBench_no_opt 1540.9 ± 8.7 1528.8 1558.3 7.02 ± 0.05
pr_revm_SstoreBench_no_opt 220.6 ± 3.6 218.6 230.6 1.00 ± 0.02
pr_levm_SstoreBench_no_opt 1548.1 ± 16.7 1533.6 1588.1 7.05 ± 0.08

@github-actions
Copy link

github-actions bot commented Nov 4, 2025

Benchmark Block Execution Results Comparison Against Main

Command Mean [s] Min [s] Max [s] Relative
base 61.827 ± 0.345 61.295 62.415 1.01 ± 0.01
head 61.495 ± 0.217 61.154 61.782 1.00

Comment on lines +258 to +260
std::mem::swap(&mut vm.stack_pool, stack_pool);
let result = vm.execute().map_err(VMError::into);
std::mem::swap(&mut vm.stack_pool, stack_pool);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to use std::mem::take at the beggining and then take. We don't need to actually swap.

@lferrigno lferrigno marked this pull request as ready for review November 6, 2025 22:28
@lferrigno lferrigno requested a review from a team as a code owner November 6, 2025 22:28
Copilot AI review requested due to automatic review settings November 6, 2025 22:28
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a stack pool optimization for transaction execution within blocks to improve performance by reusing stack allocations across multiple transactions.

  • Adds a new execute_tx_in_block function that accepts a mutable stack pool parameter for reuse across transaction executions
  • Updates execute_block_pipeline to create and maintain a shared stack pool throughout block execution
  • Swaps the stack pool with the VM before and after transaction execution to enable stack reuse

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +244 to +254
pub fn execute_tx_in_block(
// The transaction to execute.
tx: &Transaction,
// The transactions recovered address
tx_sender: Address,
// The block header for the current block.
block_header: &BlockHeader,
db: &mut GeneralizedDatabase,
vm_type: VMType,
stack_pool: &mut Vec<Stack>,
) -> Result<ExecutionReport, EvmError> {
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new public function is missing a documentation comment. Consider adding a doc comment explaining the purpose of this function and how it differs from execute_tx, particularly the role of the stack_pool parameter in optimizing stack allocations across multiple transaction executions within a block.

Copilot uses AI. Check for mistakes.
Comment on lines +258 to +260
std::mem::swap(&mut vm.stack_pool, stack_pool);
let result = vm.execute().map_err(VMError::into);
std::mem::swap(&mut vm.stack_pool, stack_pool);
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If vm.execute() panics, the second std::mem::swap won't execute, potentially leaving the stack_pool in an inconsistent state. Consider using a guard pattern or RAII wrapper to ensure the swap-back happens even in case of panics, or document that panic safety is not guaranteed here.

Suggested change
std::mem::swap(&mut vm.stack_pool, stack_pool);
let result = vm.execute().map_err(VMError::into);
std::mem::swap(&mut vm.stack_pool, stack_pool);
struct StackPoolGuard<'a> {
a: &'a mut Vec<Stack>,
b: &'a mut Vec<Stack>,
}
impl<'a> Drop for StackPoolGuard<'a> {
fn drop(&mut self) {
std::mem::swap(self.a, self.b);
}
}
std::mem::swap(&mut vm.stack_pool, stack_pool);
let _guard = StackPoolGuard { a: &mut vm.stack_pool, b: stack_pool };
let result = vm.execute().map_err(VMError::into);

Copilot uses AI. Check for mistakes.
) -> Result<BlockExecutionResult, EvmError> {
Self::prepare_block(block, db, vm_type)?;

let mut shared_stack_pool = Vec::with_capacity(1024);
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 1024 for the stack pool capacity is not explained. Consider either adding a comment explaining why this specific capacity was chosen, or extracting it as a named constant (e.g., STACK_POOL_CAPACITY) to improve code readability and maintainability.

Copilot uses AI. Check for mistakes.
pub fn execute_tx_in_block(
// The transaction to execute.
tx: &Transaction,
// The transactions recovered address
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar issue in comment: "The transactions recovered address" should be "The transaction's recovered address" (possessive form).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

levm Lambda EVM implementation performance

Projects

Status: No status
Status: Todo

Development

Successfully merging this pull request may close these issues.

5 participants